package com.util_code.retrofit;

import android.util.Base64;

import com.util_code.utils.StringUtils;
import com.google.gson.GsonBuilder;

import java.io.IOException;

import io.reactivex.schedulers.Schedulers;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;

public class RetrofitUtils {

    public static final String BASE_URL = "http://api.myservice.com/";

    /*Retrofit 2 also supports RxJava extensions. You will need to create an RxJava
     Adapter. By default, all network calls by default are synchronous:*/
    static RxJava2CallAdapterFactory rxAdapterOne = RxJava2CallAdapterFactory.create();

    /*If you wish to default network calls to be asynchronous, you need to use
    createWithScheduler().*/
    static RxJava2CallAdapterFactory rxAdapterTwo = RxJava2CallAdapterFactory.createWithScheduler(Schedulers
            .io());

    static class SingletonHolder {
        static Retrofit.Builder INSTANCE = new Retrofit.Builder().baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss")
                        .create()))
                .addCallAdapterFactory(rxAdapterOne);

    }

    public static Retrofit.Builder getInstance() {
        return SingletonHolder.INSTANCE;
    }


    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

    public static <S> S createService(Class<S> serviceClass) {
        return getInstance().build().create(serviceClass);
    }

    public static <S> S createService(Class<S> serviceClass, String username, String password) {
        if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
            String credentials = username + ":" + password;
            final String basic = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);

            httpClient.addInterceptor(new Interceptor() {
                @Override
                public okhttp3.Response intercept(Chain chain) throws IOException {
                    Request original = chain.request();

                    Request.Builder requestBuilder = original.newBuilder()
                            .header("Authorization", basic)
                            .method(original.method(), original.body());

                    Request request = requestBuilder.build();
                    return chain.proceed(request);
                }
            });
        }

        OkHttpClient client = httpClient.build();
        Retrofit retrofit = getInstance().client(client).build();
        return retrofit.create(serviceClass);
    }

    public static <S> S createService(Class<S> serviceClass, final String authToken) {
        if (authToken != null) {
            httpClient.addInterceptor(new Interceptor() {
                @Override
                public okhttp3.Response intercept(Chain chain) throws IOException {
                    Request original = chain.request();
                    original.url().toString();
                    Request.Builder requestBuilder = original.newBuilder()
                            .header("Authorization", authToken)
                            .method(original.method(), original.body());
                    Request request = requestBuilder.build();
                    return chain.proceed(request);
                }
            });
        }

        OkHttpClient client = httpClient.build();
        Retrofit retrofit = getInstance().client(client).build();
        return retrofit.create(serviceClass);
    }

    private static Retrofit singleton;

    public static Retrofit getSingleton() {
        if (singleton == null) {
            synchronized (RetrofitUtils.class) {
                if (singleton == null) {
                    singleton = new Retrofit.Builder().baseUrl(BASE_URL)
                            .addConverterFactory(ScalarsConverterFactory.create())
                            .addConverterFactory(GsonConverterFactory.create(new GsonBuilder()
                                    .setDateFormat("yyyy-MM-dd HH:mm:ss")
                                    .create()))
                            .addCallAdapterFactory(rxAdapterOne)
                            .build();
                }
            }
        }
        return singleton;
    }

    /*这个类可以满足基本要求，但是，像这样毫无线程安全保护的类，如果我们把它放入多线程的环境下，肯定就会出现问题了，如何解决？我们首先会想到对getInstance
    方法加synchronized关键字，如下：
            [java] view plaincopy
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    但是，synchronized关键字锁住的是这个对象，这样的用法，在性能上会有所下降，因为每次调用getInstance()
    ，都要对对象上锁，事实上，只有在第一次创建对象的时候需要加锁，之后就不需要了，所以，这个地方需要改进。我们改成下面这个：
            [java] view plaincopy
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (instance) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    似乎解决了之前提到的问题，将synchronized关键字加在了内部，也就是说当调用的时候是不需要加锁的，只有在instance为null
    ，并创建对象的时候才需要加锁，性能有一定的提升。但是，这样的情况，还是有可能有问题的，看下面的情况：在Java
    指令中创建对象和赋值操作是分开进行的，也就是说instance = new Singleton();
    语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序，也就是说有可能JVM会为新的Singleton实例分配空间，然后直接赋值给instance
    成员，然后再去初始化这个Singleton实例。这样就可能出错了，我们以A、B两个线程为例：
    a>A、B线程同时进入了第一个if判断
    b>A首先进入synchronized块，由于instance为null，所以它执行instance = new Singleton();
    c>由于JVM内部的优化机制，JVM先画出了一些分配给Singleton实例的空白内存，并赋值给instance成员（注意此时JVM没有开始初始化这个实例），然后A
    离开了synchronized块。
    d>B进入synchronized块，由于instance此时不是null，因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
    e>此时B线程打算使用Singleton实例，却发现它没有被初始化，于是错误发生了。
    所以程序还是有可能发生错误，其实程序在运行过程是很复杂的，从这点我们就可以看出，尤其是在写多线程环境下的程序更有难度，有挑战性。我们对该程序做进一步优化：
            [java] view plaincopy
    private static class SingletonFactory{
        private static Singleton instance = new Singleton();
    }
    public static Singleton getInstance(){
        return SingletonFactory.instance;
    }
    实际情况是，单例模式使用内部类来维护单例的实现，JVM内部的机制能够保证当一个类被加载的时候，这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance
    的时候，JVM能够帮我们保证instance只被创建一次，并且会保证把赋值给instance的内存初始化完毕，这样我们就不用担心上面的问题。*/

    /*通过使用配置文件进行实例化对象
    从Driver2和CarFactory1的实现中可以看到，当有新的车加入时，需要更新Driver2和CarFactory1
    的代码也实现对新车的支持。这就违反了开闭原则（Open-Close Principle）。可以利用反射（Reflection）解决该问题。
    public class CarFactory2 {
          private static final Logger LOG = LoggerFactory.getLogger(CarFactory2.class);

          public static Car newCar() {
            Car car = null;
            String name = null;
            try {
              XMLConfiguration config = new XMLConfiguration("car.xml");
              name = config.getString("factory2.class");
            } catch (ConfigurationException ex) {
              LOG.error("Parsing xml configuration file failed", ex);
            }

            try {
              car = (Car)Class.forName(name).newInstance();
              LOG.info("Created car class name is {}", name);
            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
              LOG.error("Instantiate car {} failed", name);
            }
            return car;
          }
    }*/

/*

//登录令牌(token)的设定
token即验证令牌，每次请求都带上，refreshToken用来刷新token的，每次请求可以不带上，但是要放在移动端保存。
1.通过username，password获取token和refreshToken
2.token的有效期为2小时，refreshToken的有效期为15天
3.如果服务器端判断token过期，而refreshToken未过期，就返回错误码给客户端，则客户端通过一个特定的接口传入refreshToken参数获取新的token和refreshToken
4.如果连续15天未使用app或者用户修改了密码，则表示refreshToken过期了，则跳到登录界面，重新登录获取token和refreshToken

  OkHttp can automatically retry unauthenticated requests. When a response is 401
  Not Authorized, an Authenticator is asked to supply credentials. Implementations
  should build a new request that includes the missing credentials. If no credentials
  are available, return null to skip the retry.

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"Bearer",
  "expires_in":3600,
  "example_parameter":"example_value"
}

Access Token Request Errors
401 Unauthorized
401 errors occur when the "Authorization" header is invalid or missing. In addition to an "errors" JSON object

authorization header - missing	{'errors': [{'errorType': invalid_client,
'message': 'Authorization header required.' }],
"success": false}

authorization header invalid - "Basic" misspelled	[{'errorType': invalid_client,
"message": "Invalid authorization header format."}],
"success": false}

Refresh Token Errors
400 Bad Request
refresh_token - invalid	{"errors":[{"errorType":"invalid_grant",
"message":"Refresh token invalid: [access_token]"}],"success":false}
refresh_token - missing	{"errors":[{"errorType":"invalid_request",
"message":"Missing parameters: refresh_token"}],"success":false}

A small extract from OAuth2 spec:
The authorization server MAY issue a new refresh token, in which case
   the client MUST discard the old refresh token and replace it with the
   new refresh token.  The authorization server MAY revoke the old
   refresh token after issuing a new refresh token to the client.  If a
   new refresh token is issued, the refresh token scope MUST be
   identical to that of the refresh token included by the client in the
   request.

   Because refresh tokens are typically long-lasting credentials used to
   request additional access tokens, the refresh token is bound to the
   client to which it was issued.

// 服务端设计规范,返回HTTP的状态码为401
    public class TokenAuthenticator implements Authenticator {

        @Override
        public Request authenticate(Proxy proxy, Response response) throws IOException {

            //取出本地的refreshToken
            String refreshToken = "sssgr122222222";

            // 通过一个特定的接口获取新的token，此处要用到同步的retrofit请求
            ApiService service = ServiceManager.getService(ApiService.class);
            Call<String> call = service.refreshToken(refreshToken);

            // 要用retrofit的同步方式
            String newToken = call.execute().body();

            // 刷新Token是否成功后处理
            if (refreshResult) {
            //refresh is successful
                String newaccess="your new access token";
                // make current request with new access token
                return response.request().newBuilder()
                        .header("Authorization", newaccess)
                        .build();

            } else {
                // refresh failed , maybe you can logout user
                // returning null is critical here , because if you do not return null
                // it will try to refresh token continuously like 1000 times.
                // also you can try 2-3-4 times by depending you before logging out your user
                return null;
            }
        }

        @Override
        public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
            return null;
        }
    }


    interface TokenManager {
        String getToken();

        boolean hasToken();

        void clearToken();

        String refreshToken();
    }

    private static class TokenInterceptor implements Interceptor {
        private final TokenManager mTokenManager;

        private TokenInterceptor(TokenManager tokenManager) {
            mTokenManager = tokenManager;
        }

        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Request modifiedRequest = request;
            if (mTokenManager.hasToken()) {
                modifiedRequest = request.newBuilder()
                        .addHeader("USER_TOKEN", mTokenManager.getToken())
                        .build();
            }

            Response response = chain.proceed(modifiedRequest);
            boolean unauthorized = response.code() == 401;
            if (unauthorized) {
                mTokenManager.clearToken();
                String newToken = mTokenManager.refreshToken();

                 // 刷新Token是否成功后处理
                if (refreshResult) {
                    //refresh is successful
                    modifiedRequest = request.newBuilder()
                        .addHeader("USER_TOKEN", mTokenManager.getToken())
                        .build();
                    return chain.proceed(modifiedRequest);

                } else {
                    // refresh failed,返回RefreshToken Error的Response
                    return response;
                }
            }
            return response;
        }
    }

*/

}
